using System;
using System.Collections.Generic;
using Microsoft.SqlServer.Dts.Pipeline;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace Northwestern.SSIS.Components
{
    [DtsPipelineComponent(
    DisplayName = "RegExtractor",
    Description = "Allows user to specify a regular expression and return the first match to a new column.",
    IconResource = "Northwestern.SSIS.Components.RegExtractor.ico",
    ComponentType = ComponentType.Transform)]

    public class RegExtractor : PipelineComponent
    {
        bool cancelEvent;
        int textColumnIn_Index;

        // maps group number to output ID
        Dictionary<int, int> matchColumnOut_Indexes = new Dictionary<int, int>();


        // Map the Input/Output columns by hand, since this is an Async
        Dictionary<int, int> InputColumnMapping;

        List<string> tmp = new List<string>();


        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();

        PipelineBuffer outputBuffer;

        public override void ProvideComponentProperties()
        {
            base.ProvideComponentProperties();

            // remove any existing metadata
            base.RemoveAllInputsOutputsAndCustomProperties();

            // create the input and the output
            IDTSInput100 input = this.ComponentMetaData.InputCollection.New();
            input.Name = "Input";
            //input.ErrorRowDisposition = DTSRowDisposition.RD_RedirectRow;

            IDTSOutput100 output = this.ComponentMetaData.OutputCollection.New();
            output.Name = "Output";
            //output.ErrorRowDisposition = DTSRowDisposition.RD_RedirectRow;


            // the output is asynchronous
            output.SynchronousInputID = 0;
            //output.ExclusionGroup = 1;

            IDTSCustomProperty100 textColumnProp = ComponentMetaData.CustomPropertyCollection.New();
            textColumnProp.Name = "TextColumn";
            textColumnProp.Description = "The name of the column that contains the text data that will be checked for a regex.";


            IDTSCustomProperty100 regexProp = ComponentMetaData.CustomPropertyCollection.New();
            regexProp.Name = "Regex";
            regexProp.Description = "The Regular expression with backreference you would like to extract.";

            IDTSCustomProperty100 OptionsProp = ComponentMetaData.CustomPropertyCollection.New();
            OptionsProp.Name = "RegexOptions";
            OptionsProp.Description = "Options string passed to regex.";


            IDTSCustomProperty100 FirstMatchOnlyProp = ComponentMetaData.CustomPropertyCollection.New();
            FirstMatchOnlyProp.Name = "FirstMatchOnly";
            FirstMatchOnlyProp.Description = "Only emit one match per input.";
            FirstMatchOnlyProp.Value = "True";
            /*
            // Create the error output.
            IDTSOutput100 errorOutput = ComponentMetaData.OutputCollection.New();
            errorOutput.IsErrorOut = true;
            errorOutput.Name = "ErrorOutput";
            errorOutput.SynchronousInputID = input.ID;
            errorOutput.ExclusionGroup = 1;

            ComponentMetaData.UsesDispositions = true;
             */
        }

        /// <summary>
        ///  only one column can be supported at this time
        /// </summary>
        /// <returns></returns>
        public override DTSValidationStatus Validate()
        {

            IDTSInput100 input = ComponentMetaData.InputCollection[0];
            IDTSOutput100 output = ComponentMetaData.OutputCollection[0];

            if (ComponentMetaData.InputCollection.Count != 1)
            {
                InternalFireError("There is more than one input. The metadata of this component is corrupt.");
                return DTSValidationStatus.VS_ISCORRUPT;
            }
            /*
            if (ComponentMetaData.OutputCollection[0].OutputColumnCollection.Count != 1)
            {
                InternalFireError("There is more than one output column.  This component creates a single ouptut column and that is the only output there should be. The metadata of this component is corrupt.");
                return DTSValidationStatus.VS_ISCORRUPT;
            }*/




            //
            // Make sure all the custom properties that the user is
            // supposed to define are correctly defined
            //
            if (ComponentMetaData.CustomPropertyCollection["TextColumn"].Value == null)
            {
                InternalFireError("TextColumn property must be set.");
                return DTSValidationStatus.VS_ISBROKEN;
            }


            // Now, for each custom property that specifies an input column
            // check to see that the input column exists
            if (!InputColumnExistsByName(ComponentMetaData.InputCollection[0], GetCustomPropertyString("TextColumn")))
            {
                InternalFireError("TextColumn: '" +
                    GetCustomPropertyString("TextColumn") +
                    "' must be one of the input columns but is not found in the input");
                return DTSValidationStatus.VS_ISBROKEN;
            }
            if (ComponentMetaData.CustomPropertyCollection["Regex"].Value == null)
            {
                InternalFireError("Regex property must be set.");
                return DTSValidationStatus.VS_ISBROKEN;
            }
            else
            {
                try { Regex test = new Regex(GetCustomPropertyString("Regex")); }
                catch {
                    InternalFireError("Regex is not valid.");
                    return DTSValidationStatus.VS_ISBROKEN;
                }
            }
            if (ComponentMetaData.CustomPropertyCollection["RegexOptions"].Value != null)
            {
                try { RegexOptions test = GetRegexOptions( GetCustomPropertyString("RegexOptions")); }
                catch
                {
                    InternalFireError("RegexOptions String is not valid.");
                    return DTSValidationStatus.VS_ISBROKEN; 
                }
            }
            foreach (IDTSInputColumn100 col in input.InputColumnCollection)
            {
                if (!OutputColumnExists(output, col.LineageID))
                {
                    ComponentMetaData.FireError(0, ComponentMetaData.Name, "The input column " + col.Name + " is not found in the " + output.Name + " output.", "", 0, out cancelEvent);
                    return DTSValidationStatus.VS_NEEDSNEWMETADATA;
                }

            }
            /// Finally, call the base class, which validates that the LineageID of each column in the input collection
            /// matches the LineageID of a column in the VirtualInputColumnCollection. 
            return base.Validate();
        }

        #region ReinitializeMetaData
        /// <summary>
        /// Called when VS_NEEDSNEWMETADATA is returned from Validate. 
        /// Reset all of the output columns.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public override void ReinitializeMetaData()
        {
            PruneOutputColumns();
            AddInputColumnsToOutput(ComponentMetaData.OutputCollection[0].ID);
            AddOutputColums();

        }
        #endregion

        #region SetComponentProperty
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.Exception.#ctor(System.String)")]
        public override IDTSCustomProperty100 SetComponentProperty(
            string propertyName,
            Object propertyValue
        )
        {

            IDTSCustomProperty100 returnVal = base.SetComponentProperty(propertyName, propertyValue);

            // Each of these properties can change the input or the output columns
            //  so callReinitializeMetaData when they are callec
            if (propertyName == "Regex" || propertyName == "RegexOptions" || propertyName == "TextColumn")
            {
                ReinitializeMetaData();
            }

            return returnVal;
        }
        # endregion

        #region PreExecute
        /// <summary>
        /// Called prior to PrimeOutput and ProcessInput. 
        /// Creates the internal buffer and stores the buffer column indexs of the input and output columns
        /// </summary>
        public override void PreExecute()
        {
            //
            // Init and fill the ColumnMapping Dictionary
            // For all the columns in the input that are also present it the ouput
            // this stores a mapping of input indices to output indices
            InputColumnMapping = new Dictionary<int, int>();
            foreach (IDTSInputColumn100 inCol in ComponentMetaData.InputCollection[0].InputColumnCollection)
            {
                foreach (IDTSOutputColumn100 outCol in ComponentMetaData.OutputCollection[0].OutputColumnCollection)
                {
                    if (inCol.Name.Equals(outCol.Name))
                    {

                        int inIndex = this.BufferManager.FindColumnByLineageID(ComponentMetaData.InputCollection[0].Buffer, inCol.LineageID);
                        int outIndex = this.BufferManager.FindColumnByLineageID(ComponentMetaData.OutputCollection[0].Buffer, outCol.LineageID);
                        InputColumnMapping[inIndex] = outIndex;
                        break;
                    }
                }
            }

            FindIndices();
        }
        #endregion

        #region PrimeOutput
        /// <summary>
        /// Called after PreExecute. The data flow task provides a buffer for each output 
        /// that is connected to a downstream component. Check the location of each output specified in the
        /// outputIDs array, and based on the index of the output cache the output buffer to the appropriate
        /// private member variable.
        /// </summary>
        /// <param name="outputs">The number of outputs.</param>
        /// <param name="outputIDs">An array of output ids.</param>
        /// <param name="buffers">An array of output buffers.</param>
        public override void PrimeOutput(int outputs, int[] outputIDs, PipelineBuffer[] buffers)
        {
            if (buffers.Length != 0)
                outputBuffer = buffers[0];
            else
                throw new Exception("No output buffer defined.");

        }
        #endregion

        public override void ProcessInput(int inputID, PipelineBuffer buffer)
        {
            IDTSInput100 input = ComponentMetaData.InputCollection.GetObjectByID(inputID);

            // This code sample assumes the component has two outputs, one the default,
            // the other the error output. If the errorOutputIndex returned from GetErrorOutputInfo
            // is 0, then the default output is the second output in the collection.
            int defaultOutputID = -1;
            int errorOutputID = -1;
            int errorOutputIndex = -1;

            GetErrorOutputInfo(ref errorOutputID, ref errorOutputIndex);

            if (errorOutputIndex == 0)
                defaultOutputID = ComponentMetaData.OutputCollection[1].ID;
            else
                defaultOutputID = ComponentMetaData.OutputCollection[0].ID;


                while (buffer.NextRow())
                {
                    //try
                    //{
                        // Do the regex match

                    // Text that will be matched
                    string inputText;


                    if (buffer[textColumnIn_Index] == null) {
                        // Just emit a NULL row by creating a row
                        // by creating the row, then copying all input fields
                        // to the output. (no regex outpupt)

                        // Add a row to the outputBuffer
                        outputBuffer.AddRow();

                        // and copy all input columns into output (pseudo synchronous)
                        CopyInputToOutput(buffer, outputBuffer, InputColumnMapping);
                        continue;
                    }
                    else if (buffer[textColumnIn_Index].GetType().Name == "BlobColumn")
                    {
                        BlobColumn InCol = (BlobColumn)buffer[textColumnIn_Index];
                        byte[] inputTextBytes = InCol.GetBlobData(0, (int)InCol.Length);
                        inputText = System.Text.Encoding.ASCII.GetString(inputTextBytes);
                    }
                    else
                    {
                        inputText = buffer[textColumnIn_Index].ToString();
                    }
                    
                        Regex regex = GetRegex();

                        // will use MatchCollection for asynch component
                        MatchCollection matchC = regex.Matches(inputText);

                        
                        //output nulls in each column if there are no matches
                        if (matchC.Count == 0)
                        {
                            // Add a row to the outputBuffer
                            outputBuffer.AddRow();

                            // and copy all input columns into output (pseudo synchronous)
                            CopyInputToOutput(buffer, outputBuffer, InputColumnMapping);

                        }
                        else{

                            // If there is a match, add a row for each match
                            // BUT, see break at end if FirstMatchOnly is set to true
                            foreach (Match match in matchC)
                            {

                                // Add a row to the outputBuffer
                                outputBuffer.AddRow();

                                // and copy all input columns into output (pseudo synchronous)
                                CopyInputToOutput(buffer, outputBuffer, InputColumnMapping);

                                foreach (int MatchNo in GetMatchOutputNameMap().Keys)
                                {
                                    // 
                                    string matchString = match.Groups[MatchNo].Value.ToString();
                                    if (matchString != "")
                                        outputBuffer.SetString(matchColumnOut_Indexes[MatchNo], matchString);
                                    else
                                        outputBuffer.SetNull(matchColumnOut_Indexes[MatchNo]);

                                }
                                if (GetCustomPropertyString("FirstMatchOnly").ToLower() == "true")
                                    break;
                            }
                        }

                        // Unless an exception occurs, direct the row to the default 
                        //outputBuffer.DirectRow(defaultOutputID);
                    //}
                    /*catch
                    {
                        // Yes, has the user specified to redirect the row?
                        if (input.ErrorRowDisposition == DTSRowDisposition.RD_RedirectRow)
                        {
                            // Yes, direct the row to the error output.
                            // TODO: Add code to include the errorColumnIndex.
                            int errorColumnIndex = matchColumnOut_Index;
                            buffer.DirectErrorRow(errorOutputID, 100, errorColumnIndex);
                        }
                        else if (input.ErrorRowDisposition == DTSRowDisposition.RD_FailComponent || input.ErrorRowDisposition == DTSRowDisposition.RD_NotUsed)
                        {
                            // No, the user specified to fail the component, or the error row disposition was not set.
                            throw new Exception("An error occurred, and the DTSRowDisposition is either not set, or is set to fail component.");
                        }
                        else
                        {
                            // No, the user specified to ignore the failure so 
                            // direct the row to the default output.
                            buffer.DirectRow(defaultOutputID);
                        }

                    }*/
                }
                // EndOfRowset on the input buffer is true.
                // Set EndOfRowset on the output buffer.
                if (buffer.EndOfRowset)
                {
                    outputBuffer.SetEndOfRowset();
                }

        }

        #region Helpers
        private void InternalFireError(string Message)
        {
            ComponentMetaData.FireError(0, ComponentMetaData.Name, Message, "", 0, out cancelEvent);
        }
        /// <summary>
        /// Searches the output column collection for a column with the name of
        /// a given string
        /// </summary>
        /// <param name="Output">The output to search.</param>
        /// <param name="ColumnName">The name of the column to search for.</param>
        /// <returns>true if found; otherwise false.</returns>
        private static bool OutputColumnExistsByName(IDTSOutput100 Output, string ColumnName)
        {
            foreach (IDTSOutputColumn100 col in Output.OutputColumnCollection)
            {
                if (col.Name == ColumnName)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Searches the output column collection for a column with the name of
        /// a given string
        /// </summary>
        /// <param name="Input">The Input to search.</param>
        /// <param name="ColumnName">The name of the column to search for.</param>
        /// <returns>true if found; otherwise false.</returns>
        private static bool InputColumnExistsByName(IDTSInput100 Input, string ColumnName)
        {
            foreach (IDTSInputColumn100 col in Input.InputColumnCollection)
            {
                if (col.Name == ColumnName)
                {
                    return true;
                }
            }

            return false;
        }


        /// <summary>
        /// Returns a string value of a custom property.  Returns null if the custom property 
        /// does not exists or the value is not set
        /// </summary>
        /// <param name="CustomProperty">The CustomProperty to return.</param>
        /// <returns>true if found; otherwise false.</returns>
        private string GetCustomPropertyString(string CustomProperty)
        {
            if (ComponentMetaData.CustomPropertyCollection[CustomProperty] == null)
                return null;

            else if (ComponentMetaData.CustomPropertyCollection[CustomProperty].Value == null)
                return null;
            else
                return ComponentMetaData.CustomPropertyCollection[CustomProperty].Value.ToString();

        }
        private static IDTSInputColumn100 GetInputColumnByName(IDTSInput100 Input, string ColumnName)
        {
            foreach (IDTSInputColumn100 col in Input.InputColumnCollection)
            {
                if (col.Name == ColumnName)
                {
                    return col;
                }
            }

            return null;
        }
        /// <summary>
        /// For each column in the input collection, add a corresponding column to the 
        /// output column collection of the specified output object.
        /// </summary>
        /// <param name="outputID">The ID of the output to add the columns to.</param>
        private void AddInputColumnsToOutput(int outputID)
        {
            IDTSInput100 input = ComponentMetaData.InputCollection[0];
            IDTSOutput100 output = ComponentMetaData.OutputCollection.GetObjectByID(outputID);

            foreach (IDTSInputColumn100 Col in input.InputColumnCollection)
            {
                if ( !OutputColumnExistsByName(output, Col.Name) ) {
                    AddOutputColumn(outputID, Col);
                }
            }
        }


        /// <summary>
        /// Creates an output column in the output identified by the OutputID parameter.
        /// Adds a custom property to the output containing the lineageID of the input column. This property is
        /// used during Validate, to verify that each input column has a matching output column.
        /// </summary>
        /// <param name="OutputID">The ID of the output to create the column in.</param>
        /// <param name="inputColumn">The input column used to set the properties of the output column.</param>
        private void AddOutputColumn(int OutputID, IDTSInputColumn100 inputColumn)
        {
            IDTSOutput100 output = ComponentMetaData.OutputCollection.GetObjectByID(OutputID);
            IDTSOutputColumn100 outputColumn = output.OutputColumnCollection.New();
            outputColumn.Name = inputColumn.Name;

            // Store the lineageID of the input column in a custom property of the output column.
            IDTSCustomProperty100 inputColLineageID = outputColumn.CustomPropertyCollection.New();
            inputColLineageID.Name = "InputColumnLineageID";
            inputColLineageID.Value = inputColumn.LineageID;

            outputColumn.SetDataTypeProperties(inputColumn.DataType, inputColumn.Length, inputColumn.Precision, inputColumn.Scale, inputColumn.CodePage);
        }
        /// <summary>
        // Gets the column names from the regex match group
        // and adds a column for each backreference
        /// </summary>
        private void AddOutputColums()
        {
            IDTSOutput100 output = ComponentMetaData.OutputCollection[0];
 

            // get list of desired output column names
            // based on the regular expression provided
            foreach (string colName in GetMatchOutputNameMap().Values)
            {
                // add the column if it does not already exists
                // in the output
                if (!OutputColumnExistsByName( output, colName)) {
                    // add column output
                    IDTSOutputColumn100 output_txt = ComponentMetaData.OutputCollection[0].OutputColumnCollection.New();
                    output_txt.Name = colName;
                    output_txt.SetDataTypeProperties(DataType.DT_STR, 1000, 0, 0, 1252);
                }
            }
        }

        /// <summary>
        // prunes all output columns that are no longer in the output
        //
        // Looks for changed input columns as well as changed regex output
        /// </summary>
        private void PruneOutputColumns()
        {


            // get the list of current output columns attached to the
            // data flow.
            IDTSOutputColumnCollection100 currentOutputColumns = ComponentMetaData.OutputCollection[0].OutputColumnCollection;
 
            // list to store columns no longer used (mark for deletion)
            List<int> colIDToRemove = new List<int>();

            Dictionary<int, string>.ValueCollection newOutputColumns = GetMatchOutputNameMap().Values;

            // loop through the current output columns mark any columns
            // for deletion that are not in desiredOutputColumns
            foreach (IDTSOutputColumn100 currCol in currentOutputColumns)
            {
                bool remove = true;

                // loop through list of desireird output colum names based on 
                // the regular expression
                foreach (string newColName in newOutputColumns)
                {
                    // if the current column in the output
                    // matches a name in the new desired column
                    // list,  set remove flag to false
                    if (currCol.Name == newColName)
                    {
                        remove = false;
                        break;
                    }

                }

                // loop through list of input columns and flag
                // the regular expression
                foreach (IDTSInputColumn100 inCol in ComponentMetaData.InputCollection[0].InputColumnCollection)
                {
                    // if the current column in the output
                    // matches a name in the new desired column
                    // list,  set remove flag to false
                    if (currCol.Name == inCol.Name)
                    {
                        // don't prune this column
                        remove = false;
                        // In our output columns, we track the lineage id of the input column that is mapped to this output column
                        // here, we make sure it is set to the proper  value
                        currCol.CustomPropertyCollection["InputColumnLineageID"].Value = inCol.LineageID;
                        break;
                    }

                }
                // if a remove flag is true, column is no longer
                // needed and added to list of columns to remove
                if (remove == true)
                {
                    colIDToRemove.Add(currCol.ID);
                }

            }

            // now, go through and remove columns.  For some reason
            // you can't remove the columns in the above loop, it
            // gives an error.  Probably cant modify a list you are
            // iterating over.
            foreach (int id in colIDToRemove)
            {

                currentOutputColumns.RemoveObjectByID(id);
            }

        }

        /// <summary>
        // Creates a dictionary keyed on match number
        // with a value of the output column name
        /// </summary>
        private Dictionary<int,string> GetMatchOutputNameMap()
        {
            Regex regex = GetRegex();

            Dictionary<int,string> columnMap = new Dictionary<int,string>();
            // create column names from group names

            int numlen = regex.GetGroupNumbers().Length;
            int namelen = regex.GetGroupNames().Length;
            foreach (int i in regex.GetGroupNumbers())
            {
                string matchName = regex.GroupNameFromNumber(i);
                if (matchName == i.ToString())
                {
                    columnMap[i] = "match_" + matchName;
                }
                else
                    columnMap[i] = matchName;
            }
            return columnMap;

        }

        private Regex GetRegex()
        {
            string regexStr = GetCustomPropertyString("Regex");

            string optionsString = "";

            if (GetCustomPropertyString("RegexOptions") != null)
            {
                optionsString = GetCustomPropertyString("RegexOptions");
            }

            RegexOptions opt = GetRegexOptions(optionsString);

            return new Regex(regexStr,opt);

        }

        /// <summary>
        // Finds index number of important columns
        // that are needed at run-time. Stashes thes
        // in class members.
        /// </summary>
        private void FindIndices()
        {
            textColumnIn_Index = -1;


            //
            // Look at the input row and identify the index of important columns
            //  that will be used in this component
            //
            foreach (IDTSInputColumn100 col in ComponentMetaData.InputCollection[0].InputColumnCollection)
            {
                int index = this.BufferManager.FindColumnByLineageID(ComponentMetaData.InputCollection[0].Buffer, col.LineageID);
                string name = col.Name;
                if (name == GetCustomPropertyString("TextColumn"))
                    textColumnIn_Index = index;

            }



            //
            // Identify the index of each output column containing regex matches
            //
            foreach (IDTSOutputColumn100 col in ComponentMetaData.OutputCollection[0].OutputColumnCollection)
            {
                int index = this.BufferManager.FindColumnByLineageID(ComponentMetaData.OutputCollection[0].Buffer, col.LineageID);
                string name = col.Name;

                Dictionary<int, string> ColumnMap = GetMatchOutputNameMap();
                foreach (int MatchNo in ColumnMap.Keys)
                {
                    if (name == ColumnMap[MatchNo])
                        matchColumnOut_Indexes[MatchNo] = index;
                }
            }

        }

        /// <summary>
        // Regex options must be captured as a string, but 
        // must be passed to regex as an Enumeration.
        // This function creates the Enumeration from the string.
        /// Courtesy of FernandoSoto on Experts Exchange
        /// </summary>
        /// <param name="opt">The string representing the Regex Options.</param>
        /// <returns>A Regex options enumerator.</returns>
        private RegexOptions GetRegexOptions(string opt)
        {
            RegexOptions retOpt = RegexOptions.None;
            String[] strOpt = opt.Split(new String[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
            foreach (String regexOpt in strOpt)
            {
                switch (regexOpt.Trim())
                {
                    case "RegexOptions.Compiled":
                        retOpt |= RegexOptions.Compiled;
                        break;
                    case "RegexOptions.CultureInvariant":
                        retOpt |= RegexOptions.CultureInvariant;
                        break;
                    case "RegexOptions.ECMAScript":
                        retOpt |= RegexOptions.ECMAScript;
                        break;
                    case "RegexOptions.ExplicitCapture":
                        retOpt |= RegexOptions.ExplicitCapture;
                        break;
                    case "RegexOptions.IgnoreCase":
                        retOpt |= RegexOptions.IgnoreCase;
                        break;
                    case "RegexOptions.IgnorePatternWhitespace":
                        retOpt |= RegexOptions.IgnorePatternWhitespace;
                        break;
                    case "RegexOptions.Multiline":
                        retOpt |= RegexOptions.Multiline;
                        break;
                    case "RegexOptions.RightToLeft":
                        retOpt |= RegexOptions.RightToLeft;
                        break;
                    case "RegexOptions.Singleline":
                        retOpt |= RegexOptions.Singleline;
                        break;
                    default:
                        break;
                }
            }

            return retOpt;
        }


        /// <summary>
        /// Searches the output column collection for a column containing the provided lineageID
        /// in the InputColumnLineageID custom property.
        /// </summary>
        /// <param name="LineageID">The lineageID of the input column.</param>
        /// <param name="output">The IDTSOutput to search.</param>
        /// <returns>true if found; otherwise false.</returns>
        private static bool OutputColumnExists(IDTSOutput100 output, int LineageID)
        {
            foreach (IDTSOutputColumn100 col in output.OutputColumnCollection)
            {

                if (CustomPropertyExists(col.CustomPropertyCollection, "InputColumnLineageID") &&
                    col.CustomPropertyCollection["InputColumnLineageID"].Value != null &&
                    (int)col.CustomPropertyCollection["InputColumnLineageID"].Value == LineageID)
                {
                    return true;
                }
            }

            return false;
        }


        /// <summary>
        /// Searches the output column collection for a column containing the provided lineageID
        /// in the InputColumnLineageID custom property.
        /// </summary>
        /// <param name="CustomPropertyCollection">The custom property collection to search.</param>
        /// <param name="CustomPropertyName">The name of the custom property to search for.</param>
        /// <returns>true if found; otherwise false.</returns>
        private static bool CustomPropertyExists(IDTSCustomPropertyCollection100 CustomPropertyCollection, string CustomPropertyName)
        {
            foreach (IDTSCustomProperty100 property in CustomPropertyCollection)
            {
                if (property.Name == CustomPropertyName)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Copies data from input buffer to output buffer given mapping between the two
        /// The mapping is a Dictionary<int, int> keyed on the input column id and 
        /// valued with the output column id
        /// </summary>
        /// <param name="input">input</param>
        /// <param name="output"></param>
        /// <param name="columnMap"></param>
        private static void CopyInputToOutput(PipelineBuffer input, PipelineBuffer output, Dictionary<int, int> columnMap)
        {
            // ...then copy data from all input columns
            // to output columns
            foreach (int inIndex in columnMap.Keys)
            {
                object oValue = input[inIndex];
                if (oValue != null)
                    if (!(oValue is BlobColumn))
                    {
                        output[columnMap[inIndex]] = oValue;
                    }
                    // Blob (text) columns must be treated differently
                    else
                    {
                        BlobColumn BlobCol = (BlobColumn)oValue;
                        output.AddBlobData(columnMap[inIndex], BlobCol.GetBlobData(0, (int)BlobCol.Length));
                    }
            }
        }

        #endregion
    }
}
